package org.finesys.common.excel.handler;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import org.finesys.common.excel.annotaion.ResponseExcel;
import org.finesys.common.excel.annotaion.Sheet;
import org.finesys.common.excel.aop.DynamicNameAspect;
import org.finesys.common.excel.converter.DictTypeConverter;
import org.finesys.common.excel.converter.LocalDateStringConverter;
import org.finesys.common.excel.converter.LocalDateTimeStringConverter;
import org.finesys.common.excel.converter.LocalTimeStringConverter;
import org.finesys.common.excel.converter.LongStringConverter;
import org.finesys.common.excel.enhance.WriterBuilderEnhancer;
import org.finesys.common.excel.exception.ExcelException;
import org.finesys.common.excel.head.HeadGenerator;
import org.finesys.common.excel.head.HeadMeta;
import org.finesys.common.excel.properties.ExcelConfigProperties;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.lang.Assert;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.util.StringUtils;
import cn.idev.excel.write.builder.ExcelWriterBuilder;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.metadata.WriteSheet;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@Slf4j
public abstract class AbstractSheetWriteHandler implements SheetWriteHandler, ApplicationContextAware {
    private final ExcelConfigProperties configProperties;

    private final ObjectProvider<List<Converter<?>>> converterProvider;

    private final WriterBuilderEnhancer excelWriterBuilderEnhance;

    private ApplicationContext applicationContext;

    @Getter
    @Setter
    @Autowired(required = false)
    private I18nHeaderCellWriteHandler i18nHeaderCellWriteHandler;


    /**
     * 校验
     *
     * @param responseExcel 注解
     */
    @Override
    public void check(ResponseExcel responseExcel) {
        if (responseExcel.sheets().length == 0) {
            throw new ExcelException("@ResponseExcel 配置不合法！");
        }
    }

    @Override
    @SneakyThrows
    public void export(Object o, HttpServletResponse response, ResponseExcel responseExcel) {
        //校验是否合法
        check(responseExcel);
        //获取请求信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //获取文件名
        String name = (String) Objects.requireNonNull(requestAttributes).getAttribute(DynamicNameAspect.EXCEL_NAME_KEY, RequestAttributes.SCOPE_REQUEST);
        if (StringUtils.isEmpty(name)) {
            name = UUID.randomUUID().toString();
        }
        String fileName = String.format("%s%s", URLEncoder.encode(name, "UTF-8"), responseExcel.suffix().getValue());
        //根据实际的文件类型找到对应的contentType
        String contentType = MediaTypeFactory.getMediaType(fileName).map(MediaType::getType).orElse("application/vnd.ms-excel");
        //响应头设置
        response.setContentType(contentType);
        response.setCharacterEncoding("UTF-8");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + fileName);
        write(o, response, responseExcel);
    }


    /**
     * 通用的获取ExcelWriter方法
     *
     * @param response      HttpServletResponse
     * @param responseExcel ResponseExcel注解
     * @return ExcelWriter
     */
    @SneakyThrows
    public ExcelWriter getExcelWriter(HttpServletResponse response, ResponseExcel responseExcel) {
        ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream())
                .registerConverter(LocalDateTimeStringConverter.INSTANCE)
                .registerConverter(LocalDateStringConverter.INSTANCE)
                .registerConverter(LocalTimeStringConverter.INSTANCE)
                .registerConverter(LongStringConverter.INSTANCE)
                .registerConverter(DictTypeConverter.INSTANCE)
                .autoCloseStream(true)
                .excelType(responseExcel.suffix())
                .inMemory(responseExcel.inMemory());
        //文件密码
        if (!StringUtils.isEmpty(responseExcel.password())) {
            excelWriterBuilder.password(responseExcel.password());
        }
        //包含字段
        if (responseExcel.include().length != 0) {
            excelWriterBuilder.includeColumnFieldNames(Arrays.asList(responseExcel.include()));
        }
        //排除字段
        if (responseExcel.exclude().length != 0) {
            excelWriterBuilder.excludeColumnFieldNames(Arrays.asList(responseExcel.exclude()));
        }
        //开启国际化
        if (responseExcel.isI18nHeader() && i18nHeaderCellWriteHandler != null) {
            excelWriterBuilder.registerWriteHandler(i18nHeaderCellWriteHandler);
        }
        // 自定义注入的转换器
        registerCustomConverter(excelWriterBuilder);
        //注入自定义转换器
        for (Class<? extends Converter<?>> converterClass : responseExcel.converter()) {
            excelWriterBuilder.registerConverter(BeanUtils.instantiateClass(converterClass));
        }
        // 注册 Workbook 清空Dict处理器
        excelWriterBuilder.registerWriteHandler(new DictCacheClearSheetWriteHandler());
        //获取模版
        String templatePath = configProperties.getTemplatePath();
        if (!StringUtils.isEmpty(templatePath)) {
            ClassPathResource classPathResource = new ClassPathResource(templatePath + File.separator + responseExcel.template());
            InputStream inputStream = classPathResource.getStream();
            excelWriterBuilder.withTemplate(inputStream);
        }
        //excel增强
        excelWriterBuilder = excelWriterBuilderEnhance.enhanceExcel(excelWriterBuilder, response, responseExcel, templatePath);
        return excelWriterBuilder.build();
    }

    /**
     * 自定义注入转换器 如果有需要，子类自己重写
     *
     * @param excelWriterBuilder ExcelWriterBuilder
     */
    private void registerCustomConverter(ExcelWriterBuilder excelWriterBuilder) {
        converterProvider.ifAvailable(converters -> converters.forEach(excelWriterBuilder::registerConverter));
    }

    /**
     * 获取 WriteSheet 对象
     *
     * @param sheet                 sheet annotation info
     * @param dataClass             数据类型
     * @param template              模板
     * @param bookHeadEnhancerClass 自定义头处理器
     * @return WriteSheet
     */
    @SneakyThrows
    public WriteSheet sheet(Sheet sheet, Class<?> dataClass, String template,
                            Class<? extends HeadGenerator> bookHeadEnhancerClass) {

        // Sheet 编号
        Integer sheetNo = sheet.sheetNo() >= 0 ? sheet.sheetNo() : null;
        //Sheet 名称
        String sheetName = sheet.sheetName();
        // 是否模板写入
        ExcelWriterSheetBuilder excelWriterBuilder = !StringUtils.isEmpty(template) ? EasyExcel.writerSheet(sheetNo) : EasyExcel.writerSheet(sheetNo, sheetName);
        // 头信息增强 1. 优先使用 sheet 指定的头信息增强 2. 其次使用 @ResponseExcel 中定义的全局头信息增强
        Class<? extends HeadGenerator> headGeneratorClass = null;
        if (isNotInterface(sheet.headGenerator())) {
            headGeneratorClass = sheet.headGenerator();
        } else if (isNotInterface(bookHeadEnhancerClass)) {
            headGeneratorClass = bookHeadEnhancerClass;
        }
        // 定义头信息增强则使用其生成头信息，否则使用 dataClass 来自动获取
        if (headGeneratorClass != null) {
            fillCustomHeadInfo(dataClass, bookHeadEnhancerClass, excelWriterBuilder);
        } else if (dataClass != null) {
            excelWriterBuilder.head(dataClass);
            if (sheet.includes().length > 0) {
                excelWriterBuilder.includeColumnFieldNames(Arrays.asList(sheet.includes()));
            }
            if (sheet.excludes().length > 0) {
                excelWriterBuilder.excludeColumnFieldNames(Arrays.asList(sheet.excludes()));
            }
        }
        // sheetBuilder 增强
        excelWriterBuilder = excelWriterBuilderEnhance.enhanceSheet(excelWriterBuilder, sheetNo, sheetName, dataClass, template, headGeneratorClass);
        return excelWriterBuilder.build();
    }

    /**
     * 填充表头
     */
    private void fillCustomHeadInfo(Class<?> dataClass, Class<? extends HeadGenerator> bookHeadEnhancerClass, ExcelWriterSheetBuilder excelWriterBuilder) {
        HeadGenerator headGenerator = this.applicationContext.getBean(bookHeadEnhancerClass);
        Assert.notNull(headGenerator, "The header generated bean does not exist!");
        HeadMeta headMeta = headGenerator.head(dataClass);
        excelWriterBuilder.head(headMeta.getHead());
        excelWriterBuilder.excludeColumnFieldNames(headMeta.getIgnoreHeadFields());
    }

    /**
     * 写入数据
     */
    public void writeData(List<?> dataList, ExcelWriter excelWriter, WriteSheet sheet, ResponseExcel responseExcel) {
        //填充
        if (responseExcel.fill()) {
            excelWriter.fill(dataList, sheet);
        } else {
            //写入
            excelWriter.write(dataList, sheet);
        }
    }

    /**
     * 是否为Null Head Generator
     *
     * @param aClass 头生成器类型
     * @return true 已指定 false 未指定(默认值)
     */
    private boolean isNotInterface(Class<? extends HeadGenerator> aClass) {
        return !Modifier.isInterface(aClass.getModifiers());
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
